﻿using System;
using System.Collections.Generic;
using System.Linq;
using Gumini.JulaDB.DataTypes;
using Gumini.JulaDB.Enums;
using Gumini.JulaDB.Provider;

namespace Gumini.JulaDB.Expressions
{
    /// <summary>
    /// Abstract base class for a node in an expression tree.
    /// </summary>
    internal abstract class Expression : IEquatable<Expression>
    {
        protected readonly List<Expression> _children = new List<Expression>();

        protected Expression(params Expression[] children)
        {
            _children.AddRange(children);
        }

        /// <summary>
        /// Calculates value of a collection of expressions that come from multiple rows.
        /// Can be used for scalar expressions (single context) or aggregate functions (multiple contexts).
        /// </summary>
        public abstract TypedValue Evaluate(List<Context<TypedValue>> contexts);

        /// <summary>
        /// Calculates value of an expression within a specific single row context.
        /// Can be used for scalar expressions.
        /// </summary>
        public TypedValue Evaluate(Context<TypedValue> context)
        {
            return this.Evaluate(new List<Context<TypedValue>> { context });
        }

        /// <summary>
        /// Gets the type of value that will be returned as a result of the expression evaluation.
        /// Will throw an exception if there is a typechecking error.
        /// Might return null if the expression type is unknown (e.g. when null constant is used in expression).
        /// </summary>
        public abstract DataType InferType(Context<DataType> context);

        /// <summary>
        /// Returns true if expresion requires a group of rows instead of a single row.
        /// </summary>
        public bool IsAggregate
        {
            get
            {
                var areChildrenAggregates = _children.Select(c => c.IsAggregate);
                // no children are aggregates
                if (areChildrenAggregates.All(a => !a))
                    return this.IsAggregateCore;
                // the expression is not an aggregate, but all children are aggregates
                if (areChildrenAggregates.All(a => a) && !this.IsAggregateCore)
                    return true;
                throw new JulaDbException(ErrorCode.InvalidAggregateExpression, "Aggregate expression cannot be nested.");
            }
        }

        /// <summary>
        /// This is intended to be overriden by aggregate function call node.
        /// </summary>
        protected virtual bool IsAggregateCore
        {
            get { return false; }
        }

        public override bool Equals(object obj)
        {
            return this.Equals(obj as Expression);
        }

        public abstract override string ToString();

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public abstract bool Equals(Expression other);
    }
}
